namespace MultiLineComments
{

    using System;
    using System.Runtime.InteropServices;
    using System.Text.RegularExpressions;
    using System.Diagnostics;
    using System.Globalization;
    using System.ComponentModel.Design;
    using Microsoft.Win32;
    using Microsoft.VisualStudio.OLE.Interop;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.Shell;
    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio.TextManager.Interop;
    using System.Windows.Forms;
    using Microsoft.VisualStudio.Text.Tagging;
    using System.ComponentModel.Composition;
    using Microsoft.VisualStudio.Text;
    using Microsoft.VisualStudio.Text.Classification;
    using Microsoft.VisualStudio.Text.Editor;
    using Microsoft.VisualStudio.Text.Formatting;
    using Microsoft.VisualStudio.Utilities;
    using Microsoft.VisualStudio.Editor;
    using System.Collections.Generic;
    using Microsoft.VisualStudio.Text.Operations;

    using System.Text;
    using System.Drawing;
    using EnvDTE;
    using EnvDTE80;
    using System.Linq;


    public class LuaWrapper : BaseWrapper
    {

        enum Mode {NORMAL, INLINE_COMMENT, LONG_COMMENT, LONG_STRING};



        protected int last_version = -1;

        public LuaWrapper(IVsTextView textViewAdapter, IVsEditorAdaptersFactoryService EditorFactory) :
            base(textViewAdapter, EditorFactory) { 
        }

        class Info
        {
            public Mode m;
            public int i0, i1,j0,j1;

            public bool contains(int i)
            {
                return i <= i1 && i >= i0;
            }

            public bool inner_contains(int i)
            {
                return i <= j1 && i >= j0;
            }
        }
        List<Info> section_info = new List<Info>();

        int section0;
        Mode mode;
        char[] cdata;

        void mark_inner_section(int shift)
        {
            if (section_info.Count > 0)
            {
                Info in0 = section_info.Last();
                in0.j0 = in0.i0 + shift;
                in0.j1 = in0.i1 - shift;
 
            }
        }

        void mark_complete_section(Mode m1, int i1)
        {

            // extend inline comments
            if (section_info.Count > 0) {
                Info in0 = section_info.Last();

                // section will have already been completed by the inline
                // comment, so we're done.
                if (m1 == Mode.LONG_COMMENT)
                {
                    mode = m1;
                    return;
                }
            }

            if (section0 <= i1)
            {

                section_info.Add(new Info
                {
                    m = mode,
                    i0 = section0,
                    i1 = i1
                });

                section0 = i1 + 1;
            }
            mode = m1;


        }

        void find_long_strings()
        {
            section_info.Clear();
            mode = Mode.NORMAL;
            section0 = 0;
            int length = cdata.Length;

            bool matching_equals = false, matching_sequals=false;
            int nequals=0,nsequals=0;

            for (int i = 0; i < length; i++)
            {
                char c = cdata[i];

                if (matching_sequals)
                {
                    if (c == '=')
                    {
                        nsequals++;
                        continue;
                    }
                    else
                    {
                        matching_sequals = false;
                        if (nsequals == nequals && c==']')
                        {
                            mark_complete_section(Mode.NORMAL, i);
                            mark_inner_section(nequals + 2);
                        }
                    }
                }

                if (matching_equals)
                {
                    if (c == '=')
                    {
                        nequals++;
                        continue;
                    }
                    else
                    {
                        matching_equals = false;

                        if (c == '[')
                        {
                            if (mode == Mode.INLINE_COMMENT)
                            {
                                mark_complete_section(Mode.LONG_COMMENT, i - nequals - 4);
                                //mark_inner_section(nequals+2);
                            }
                            else
                            {

                                mark_complete_section(Mode.LONG_STRING, i - nequals - 2);
                            }
                        }
                    }
                }


                switch (mode)
                {
                    case Mode.NORMAL: 
                    if(c=='-' && (i+1<length) && cdata[i+1]=='-') {
                        mark_complete_section(Mode.INLINE_COMMENT, i-1);
                        i++;
                    }
                    else if (c == '[')
                    {
                        nequals = 0;
                        matching_equals = true;
                    }
                    break;

                    case Mode.INLINE_COMMENT:
                    if (c == '\n')
                    {
                        mark_complete_section(Mode.NORMAL, i);
                    }
                    else if (i == section0 + 2 && c == '[')
                    {
                        nequals = 0;
                        matching_equals = true;
                    }
                    break;

                    case Mode.LONG_COMMENT:
                    case Mode.LONG_STRING:
                    if (c == ']')
                    {
                        matching_sequals = true;
                        nsequals = 0;
                    }
                    break;

                }
            }

            if (mode == Mode.NORMAL || mode == Mode.INLINE_COMMENT)
            {
                mark_complete_section(mode, length - 1);
            }
            
        }

        ITextSnapshot shot;

        public override void pre_change_handling()
        {

            shot = wview.TextSnapshot;

            if (shot.Version.VersionNumber != last_version)
            {
                last_version = shot.Version.VersionNumber;

                // this is probably a miserably inefficient way to do this, but, 
                // as long as i'm careful about *when* i do it, inefficiency
                // shouldn't matter too much.         
                cdata= shot.ToCharArray(0,shot.Length);

                if (cdata != null)
                    find_long_strings();
            }
        }


        int find_section_id(int pos)
        {

            int i1 = section_info.Count-1;
            int i0 = 0;

            for ( int j=-1;; ) {

                int i=(i1+i0)>>1;

                if (i == j)
                {
                    return i + 1;
                }
                j = i;

                Info info = section_info[i];

                if( info.i1<pos ) {
                    i0=i;
                }
                else if (info.i0 > pos)
                {
                    i1 = i;
                }
                else
                {
                    return i;
                }

                

                
            }
        }

        string getCurrentText(int i0, int i1)
        {
            int r0, c0, r1, c1,start0,start1;
            lines.GetLineIndexOfPosition(i0, out r0, out c0);
            lines.GetLineIndexOfPosition(i1, out r1, out c1);

            lines.GetPositionOfLineIndex(r0,0,out start0);
            lines.GetPositionOfLineIndex(r1,0,out start1);

            string rval;
            lines.GetLineText(r0, i0 - start0, r1, i1 - start1, out rval);

            return rval;

        }

        void long_string_wrap(Info info, TextLineChange c)
        {

            if (wview.TextSnapshot.Version != shot.Version )
            {
                return;
            }

//|| wview.TextSnapshot.Version.Changes.Count>0

            // don't do any wrapping if the long string starts on the
            // current line.
            if (!info.inner_contains(linestart))
            {
                return;
            }

            if (!info.inner_contains(linestart+c.iStartIndex))
            {
                return;
            }

            if (!info.inner_contains(linestart+c.iOldEndIndex))
            {
                return;
            }

            //lines.GetLineText()


            int dchars = (c.iNewEndIndex - c.iStartIndex) - (c.iOldEndIndex - c.iStartIndex);

            string text = getCurrentText(linestart, info.j1 + dchars+1);
            if (text == null || text.Length == 0)
            {
                return;
            }

            int cpos = absolute_cursor - linestart;
            int opos = cpos;

            if (cpos < 0 && cpos >= text.Length)
            {
                return;
            }

            // stop the replacements at paragraph breaks or colons
            m = Regex.Match(text, @"\n\s*\n");
            if(m.Success) {
                text = text.Substring(0, m.Index);
            }
            m = Regex.Match(text, @":");
            if (m.Success)
            {
                text = text.Substring(0, m.Index);
            }


            m = Regex.Match(text, @"^[\t ]*");

            if (!m.Success) return;
            prefix = m.Value;

            if (prefix.Length >= cpos) return;

            //prefix = "";
            //string longline = Regex.Replace(text, "\r\n" + prefix, " ");
            //longline = Regex.Replace(text, "[^\r](\n" + prefix, " ");

            string wrapped_line = "";
            int wrapped_line_len = 0;
            int max_chars=60;
    
            int tail=0;

            foreach ( Match r in Regex.Matches(text,@"(\S+)") ) {
                string word = r.Value;
                string space = text.Substring(tail, r.Index-tail);

                if (r.Index <= opos)
                {
                    cpos -= r.Index - tail;
                }

                tail=r.Index+word.Length;

                if (wrapped_line_len == 0)
                {
                    wrapped_line = prefix + word;
                    wrapped_line_len = word.Length;
                    if (r.Index <= opos) cpos += prefix.Length;
                }
                else if (wrapped_line_len + word.Length <= max_chars)
                {
                    if (space == "  ")
                    {
                        wrapped_line += " ";
                        if (r.Index <= opos) cpos++;
                    }
                    wrapped_line += " " + word;
                    wrapped_line_len += word.Length;
                    if (r.Index <= opos) cpos += 1;
                }
                else
                {
                    if (space == " \r\n")
                    {
                        wrapped_line += " ";
                        if (r.Index <= opos) cpos++;
                    }

                    wrapped_line += "\r\n" + prefix + word;
                    wrapped_line_len = word.Length;
                    if (r.Index <= opos) cpos += prefix.Length + 2;
                }  
            }



  

            int new_cursor = cpos + linestart;
            wrapped_line = wrapped_line+text.Substring(tail);
//            wrapped_line = Regex.Replace(wrapped_line, @"\s*$", prefix+"\r\n");

            if (wrapped_line==text)
            {
                return;
            }

            ITextEdit edit = wbuffer.CreateEdit();
            if (edit == null) return;


            edit.Replace(linestart, text.Length, wrapped_line);
            edit.Apply();

            set_new_caret(new_cursor);

        }

        protected override bool wrapline(TextLineChange c)
        {
           
           // int pr, pc, vs, absolute_cursor;

           // view.GetCaretPos(out pr, out pc);
           // view.GetNearestPosition(pr, pc, out absolute_cursor, out vs);

            if (section_info.Count == 0) return true;

            if (wbuffer.EditInProgress) return false;

 

            int cid = find_section_id(absolute_cursor-1);
            if (cid < section_info.Count)
            {
                Info info = section_info[cid];

                switch (info.m)
                {
                    case Mode.LONG_STRING:  
                    long_string_wrap(info,c);
                    return true;

                }

            }


            return false;
        }
    }


}